//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift.org project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift.org project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import JExtractSwiftLib
import Testing

@Suite
struct JNIProtocolTests {
  let source = """
    public protocol SomeProtocol {
      var x: Int64 { get set }
    
      public func method() {}
    }
  
    public protocol B {}
  
    public class SomeClass: SomeProtocol {}
  
    public func takeProtocol(x: some SomeProtocol, y: any SomeProtocol)
    public func takeGeneric<S: SomeProtocol>(s: S)
    public func takeComposite(x: any SomeProtocol & B)
  """

  @Test
  func generatesJavaInterface() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        // Generated by jextract-swift
        // Swift module: SwiftModule
        
        package com.example.swift;
        
        import org.swift.swiftkit.core.*;
        import org.swift.swiftkit.core.util.*;
        """,
        """
        public interface SomeProtocol extends JNISwiftInstance {
        ...
        }
        """,
        """
        public long getX();
        """,
        """
        public void setX(long newValue);
        """,
        """
        public void method();
        """
      ])
  }

  @Test
  func generatesJavaClassWithExtends() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public final class SomeClass implements JNISwiftInstance, SomeProtocol {
        """
      ])
  }

  @Test
  func takeProtocol_java() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public static <$T0 extends SomeProtocol, $T1 extends SomeProtocol> void takeProtocol($T0 x, $T1 y) {
          SwiftModule.$takeProtocol(x.$memoryAddress(), x.$typeMetadataAddress(), y.$memoryAddress(), y.$typeMetadataAddress());
        }
        """,
        """
        private static native void $takeProtocol(long x, long x_typeMetadataAddress, long y, long y_typeMetadataAddress);
        """
      ])
  }

  @Test
  func takeProtocol_swift() throws {
    try assertOutput(
      input: source,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        @_cdecl("Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ")
        func Java_com_example_swift_SwiftModule__00024takeProtocol__JJJJ(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong, y: jlong, y_typeMetadataAddress: jlong) {
          guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else {
            fatalError("x_typeMetadataAddress memory address was null")
          }
          let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self)
          guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else {
            fatalError("x memory address was null")
          }
          #if hasFeature(ImplicitOpenExistentials)
          let xExistential$ = xRawPointer$.load(as: xDynamicType$) as! any (SomeProtocol)
          #else
          func xDoLoad<Ty>(_ ty: Ty.Type) -> any (SomeProtocol) {
            xRawPointer$.load(as: ty) as! any (SomeProtocol)
          }
          let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad)
          #endif
          guard let yTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: y_typeMetadataAddress, in: environment))) else {
            fatalError("y_typeMetadataAddress memory address was null")
          }
          let yDynamicType$: Any.Type = unsafeBitCast(yTypeMetadataPointer$, to: Any.Type.self)
          guard let yRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: y, in: environment))) else {
            fatalError("y memory address was null")
          }
          #if hasFeature(ImplicitOpenExistentials)
          let yExistential$ = yRawPointer$.load(as: yDynamicType$) as! any (SomeProtocol)
          #else
          func yDoLoad<Ty>(_ ty: Ty.Type) -> any (SomeProtocol) {
            yRawPointer$.load(as: ty) as! any (SomeProtocol)
          }
          let yExistential$ = _openExistential(yDynamicType$, do: yDoLoad)
          #endif
          SwiftModule.takeProtocol(x: xExistential$, y: yExistential$)
        }
        """
      ]
    )
  }

  @Test
  func takeGeneric_java() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public static <S extends SomeProtocol> void takeGeneric(S s) {
          SwiftModule.$takeGeneric(s.$memoryAddress(), s.$typeMetadataAddress());
        }
        """,
        """
        private static native void $takeGeneric(long s, long s_typeMetadataAddress);
        """
      ])
  }

  @Test
  func takeGeneric_swift() throws {
    try assertOutput(
      input: source,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        @_cdecl("Java_com_example_swift_SwiftModule__00024takeGeneric__JJ")
        func Java_com_example_swift_SwiftModule__00024takeGeneric__JJ(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, s: jlong, s_typeMetadataAddress: jlong) {
          guard let sTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: s_typeMetadataAddress, in: environment))) else {
            fatalError("s_typeMetadataAddress memory address was null")
          }
          let sDynamicType$: Any.Type = unsafeBitCast(sTypeMetadataPointer$, to: Any.Type.self)
          guard let sRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: s, in: environment))) else {
            fatalError("s memory address was null")
          }
          #if hasFeature(ImplicitOpenExistentials)
          let sExistential$ = sRawPointer$.load(as: sDynamicType$) as! any (SomeProtocol)
          #else
          func sDoLoad<Ty>(_ ty: Ty.Type) -> any (SomeProtocol) {
            sRawPointer$.load(as: ty) as! any (SomeProtocol)
          }
          let sExistential$ = _openExistential(sDynamicType$, do: sDoLoad)
          #endif
          SwiftModule.takeGeneric(s: sExistential$)
        }
        """
      ]
    )
  }

  @Test
  func takeComposite_java() throws {
    try assertOutput(
      input: source,
      .jni, .java,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        public static <$T0 extends SomeProtocol & B> void takeComposite($T0 x) {
          SwiftModule.$takeComposite(x.$memoryAddress(), x.$typeMetadataAddress());
        }
        """,
        """
        private static native void $takeComposite(long x, long x_typeMetadataAddress);
        """
      ])
  }

  @Test
  func takeComposite_swift() throws {
    try assertOutput(
      input: source,
      .jni, .swift,
      detectChunkByInitialLines: 1,
      expectedChunks: [
        """
        @_cdecl("Java_com_example_swift_SwiftModule__00024takeComposite__JJ")
        func Java_com_example_swift_SwiftModule__00024takeComposite__JJ(environment: UnsafeMutablePointer<JNIEnv?>!, thisClass: jclass, x: jlong, x_typeMetadataAddress: jlong) {
          guard let xTypeMetadataPointer$ = UnsafeRawPointer(bitPattern: Int(Int64(fromJNI: x_typeMetadataAddress, in: environment))) else {
            fatalError("x_typeMetadataAddress memory address was null")
          }
          let xDynamicType$: Any.Type = unsafeBitCast(xTypeMetadataPointer$, to: Any.Type.self)
          guard let xRawPointer$ = UnsafeMutableRawPointer(bitPattern: Int(Int64(fromJNI: x, in: environment))) else {
            fatalError("x memory address was null")
          }
          #if hasFeature(ImplicitOpenExistentials)
          let xExistential$ = xRawPointer$.load(as: xDynamicType$) as! any (SomeProtocol & B)
          #else
          func xDoLoad<Ty>(_ ty: Ty.Type) -> any (SomeProtocol & B) {
            xRawPointer$.load(as: ty) as! any (SomeProtocol & B)
          }
          let xExistential$ = _openExistential(xDynamicType$, do: xDoLoad)
          #endif
          SwiftModule.takeComposite(x: xExistential$)
        }
        """
      ]
    )
  }
}
